package edu.faytechcc.csc.business;

import java.io.*;
import java.util.Scanner;
import java.util.zip.*;

/**
 * This class contains static methods that help with file manipulation needed
 * for the editor. The methods include zip utilities to compress and uncompress
 * files/directories, delete a directory, and edit the lines in the element.xml
 * file included in all Alice classes.
 * 
 * @author Charles Bryan
 */
public class FileUtilities
{

    static final int BUFFER = 1024;

    /**
     * Extracts (unzips) the file passed to the method. The contents are 
     * extracted to the same directory as the zip file. Note: The File must be 
     * compressed in zip format i.e. *.rar, *.tar, etc will not work. 
     * 
     * @param zipFileFullPath The FULL path of the file to be extracted 
     * (unzipped)
     */
    public static void extract(String zipFileFullPath)
    {
        //get the path name without the file name, only directories
        int lastIndex = zipFileFullPath.lastIndexOf("\\");
        String extractionFullPath = zipFileFullPath.substring(0, lastIndex + 1);

        //extract using the other extract method to avoid duplicating code
        extract(zipFileFullPath, extractionFullPath);
    }

    /**
     * Extracts (unzips) the file passed to the method. The contents are 
     * extracted to the FULL path passed in as extractionFullPath . Note: 
     * The File must be compressed in zip format i.e. *.rar, *.tar, etc will 
     * not work. 
     * 
     * @param zipFileFullPath The FULL path of the file to be extracted 
     * (unzipped)
     * @param extractionFullPath The FULL path of where the zip contents should
     * be extracted to. 
     */
    public static void extract(String zipFileFullPath,
            String extractionFullPath)
    {
        try
        {

            //if the extraction path does not exist, creat it.
            File path = new File(extractionFullPath);
            if (!path.exists())
            {
                new File(extractionFullPath).mkdirs();
            }

            //open streams on the zip files
            BufferedOutputStream dest = null;
            FileInputStream fis = new FileInputStream(zipFileFullPath);
            ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
            //use this for each of the files that comes out of the zip
            ZipEntry entry;

            //pull out each file in the zip until all of them are out
            while ((entry = zis.getNextEntry()) != null)
            {
                int count;
                byte data[] = new byte[BUFFER];
                // write the files to the disk
                if (entry.isDirectory())
                {
                    //the "file" coming out of the zip is a directory so create it
                    new File(extractionFullPath + entry.getName()).mkdirs();
                }
                else
                {
                    //its a file so copy it bit for bit out of the zip file                  
                    FileOutputStream fos = new FileOutputStream(extractionFullPath + entry.getName());
                    dest = new BufferedOutputStream(fos, BUFFER);
                    while ((count = zis.read(data, 0, BUFFER))
                            != -1)
                    {
                        dest.write(data, 0, count);
                    }

                    //flush and close the buffer
                    dest.flush();
                    dest.close();
                }
            }
            //close the zip file
            zis.close();
        } catch (Exception e)
        {
            //hmmmmm bad way to handle exceptions. This needs to be cleaned up...            
            e.printStackTrace();
        }
    }

    /**
     * Compresses the contents of the directory passed in as dirToBeZipped 
     * into a single file with the name passed in as filename. This method will
     * recursively go into all directories in dirToBeZipped and add files and\
     * directories inside each. 
     * 
     * @param filename the FULL path of the filename the user wants the contents 
     * zipped into.
     * @param dirToBeZipped The FULL path of the directory which contents are
     * to be zipped.
     */
    public static void compress(String filename, String dirToBeZipped)
    {
        try
        {
            //open output streams to put the files/directies into
            BufferedInputStream origin = null;
            FileOutputStream dest = new FileOutputStream(filename);
            ZipOutputStream out =
                    new ZipOutputStream(new BufferedOutputStream(dest));
            
            File f = new File(dirToBeZipped);

            byte data[] = new byte[BUFFER];

            //call the recursive method to do all the work
            recursiveDirCompress(f, dirToBeZipped, origin, dest, out, data);

            //close the zip file.
            out.close();


        } catch (Exception e)
        {
            //hmmmmm bad way to handle exceptions. This needs to be cleaned up...             
            e.printStackTrace();
        }
    }

    /**
     * A private helper method that recursively compresses all of the directories
     * and file contents into the given zip file. 
     * Finally I found out the problem. It was related to the path. its really 
     * funny, but if u give the absoute path of the files to be zipped to 
     * zipoutputstream, this error happens. i tried with relative paths and
     * BINGO!!! it worked. Hence i did some work around before zipping and 
     * pointed the parent of the files to the current working directory and 
     * then zipped. Thanks all for the responses.
     * 
     * @param dir 
     * @param dirToBeZipped
     * @param origin
     * @param dest
     * @param out
     * @param data 
     */
    private static void recursiveDirCompress(File dir,
            String dirToBeZipped,
            BufferedInputStream origin,
            FileOutputStream dest,
            ZipOutputStream out,
            byte data[])
    {
        try
        {
            //get the local path of the file or dir to be added to the zip
            String localPath = dir.getPath() + "//";
            localPath = localPath.substring(dirToBeZipped.length(), localPath.length() - 1);

            // get a list of files from current directory
            String files[] = dir.list();

            //process through the list of files in teh current dir
            for (int index = 0; index < files.length; index++)
            {
                String file = dir.getPath() + "\\" + files[index];
                if (new File(file).isDirectory())
                {
                    //the "file" is a directory, recursivly call this method on it.
                    File f = new File(file + "\\");
                    recursiveDirCompress(f, dirToBeZipped, origin, dest, out, data);
                }
                else
                {
                    //The file is a "file", add it to the zip.
                    FileInputStream fi = new FileInputStream(file);
                    origin = new BufferedInputStream(fi, BUFFER);
                    ZipEntry entry = new ZipEntry(localPath + files[index]);
                    out.putNextEntry(entry);
                    int count;
                    //bit by bit, copy the file into the zip
                    while ((count = origin.read(data, 0,
                            BUFFER)) != -1)
                    {
                        out.write(data, 0, count);
                    }
                    //close the file now that it is added to the zip.
                    origin.close();
                }
            }
        } catch (Exception e)
        {
            //hmmmmm bad way to handle exceptions. This needs to be cleaned up...             
            e.printStackTrace();
        }
    }

    /**
     * This method deletes all of the files and internal directories in it. 
     * Please use carefully, everything inside will be deleted without warning.
     * 
     * @param dir The directory to be deleted, all files and directories will
     * be deleted.
     */
    public static void deleteDirectory(File dir)
    {
        //get a list of files in the dir
        String files[] = dir.list();
        File current;

        //for each file in the current dir
        for (int index = 0; index < files.length; index++)
        {
            current = new File(dir.getPath() + "\\" + files[index]);
            if (current.isDirectory())
            {
                //this "file" is a dir, recursivle call this method on it.
                deleteDirectory(current);
            }
            else
            {
                //delete the file
                current.delete();
            }
        }
        //delete the empty directory
        dir.delete();
    }

    /**
     * This method goes into the file element.xml and adds the student name and
     * section to the correct places. Currently, in the Alice Class files in
     * the Alice gallery, there are two format versions to element.xml. One 
     * version has no elements in the data element so all lines must be 
     * written. The other version contains the data elements so text must be
     * replaced. 
     * 
     * @param file the FULL path of the element.xml file to be altered.
     * @param section the section to be written into the file
     * @param name the student name to be written into the file
     */
    public static void changeData(String file, String section, String name)
    {
        try
        {
            //get the full path of the input file without the file name
            String path = file.substring(0, file.lastIndexOf("\\") + 1);

            //rename the file with a .orig extension so that the original file
            //is preserved.
            new File(file).renameTo(new File(file + ".orig"));

            PrintWriter outFile = new PrintWriter(new File(file));

            Scanner inFile = new Scanner(new File(file + ".orig"));
            //process through the file
            while (inFile.hasNext())
            {
                //process each line
                processLine(inFile.nextLine(), outFile, inFile, name, section);
            }
            
            //flush and close all of the files.
            outFile.flush();
            outFile.close();
            inFile.close();
        } catch (Exception e)
        {
             //hmmmmm bad way to handle exceptions. This needs to be cleaned up...  
        }
    }

    /**
     * A private helper method used to process each line of the file
     * @param line
     * @param outFile
     * @param inFile
     * @param name
     * @param section 
     */
    private static void processLine(String line, PrintWriter outFile,
            Scanner inFile, String name, String section)
    {
        //this class has no data proerties so all the info must be added
        if (line.contains("<property name=\"data\"/>"))
        {
            outFile.println("\t<property name=\"data\">");
            outFile.println("\t\t<entry>");
            outFile.println("\t\t\t<key class=\"java.lang.String\">"
                    + "<![CDATA[painted by]]></key>");

            String temp = "\t\t\t<value class=\"java.lang.String\">"
                    + "<![CDATA["
                    + name
                    + "]]></value>";
            outFile.println(temp);

            outFile.println("\t\t</entry>");
            outFile.println("\t\t<entry>");
            outFile.println("\t\t\t<key class=\"java.lang.String\">"
                    + "<![CDATA[modeled by]]></key>");

            temp = "\t\t\t<value class=\"java.lang.String\">"
                    + "<![CDATA["
                    + section
                    + "]]></value>";
            outFile.println(temp);

            outFile.println("\t\t</entry>");
            outFile.println("\t</property>");
        }
        else if (line.contains("painted by"))
        {
            //edit out the painted by to add the student name
            outFile.println(line);
            line = inFile.nextLine(); //move the cursor to the next line
            String temp = "\t\t\t<value class=\"java.lang.String\">"
                    + "<![CDATA["
                    + name
                    + "]]></value>";
            outFile.println(temp);
        }
        else if (line.contains("modeled by"))
        {
            //edit out the modeled by to add the section
            outFile.println(line);
            line = inFile.nextLine();//move the cursor to the next line
            String temp = "\t\t\t<value class=\"java.lang.String\">"
                    + "<![CDATA["
                    + section
                    + "]]></value>";
            outFile.println(temp);
        }
        else
        {
            //not a line we want to change so rewrite it to the file without
            //altering it.
            outFile.println(line);
        }
    }

}